<?php
declare(strict_types=1);

/**
 * Author: Jesse
 * Email : jesse_89@163.com
 * Date: 2024/06/15 20:05
 * FileName: HttpRegisterServiceListener.php
 * DESC: The Relentless Pursuit of Perfection.
 */

namespace Common\Kernel\Http;


use Hyperf\Contract\ConfigInterface;
use Hyperf\Contract\IPReaderInterface;
use Hyperf\Contract\StdoutLoggerInterface;
use Hyperf\Event\Contract\ListenerInterface;
use Hyperf\Framework\Event\MainWorkerStart;
use Hyperf\Server\Event\MainCoroutineServerStart;
use Hyperf\Server\Exception\ServerException;
use Hyperf\ServiceGovernance\DriverManager;
use Hyperf\ServiceGovernance\ServiceManager;
use Psr\Container\ContainerInterface;


/**
 * 注册http服务到nacos
 */
class HttpRegisterServiceListener implements ListenerInterface
{
    /**
     * @var StdoutLoggerInterface
     */
    protected StdoutLoggerInterface $logger;

    /**
     * @var ServiceManager
     */
    protected ServiceManager $serviceManager;

    /**
     * @var ConfigInterface
     */
    protected ConfigInterface $config;

    /**
     * @var IPReaderInterface
     */
    protected IPReaderInterface $ipReader;

    /**
     * @var DriverManager
     */
    protected DriverManager $governanceManager;

    /**
     * @param ContainerInterface $container
     * @throws \Psr\Container\ContainerExceptionInterface
     * @throws \Psr\Container\NotFoundExceptionInterface
     */
    public function __construct(ContainerInterface $container)
    {
        $this->logger = $container->get(StdoutLoggerInterface::class);
        $this->serviceManager = $container->get(ServiceManager::class);
        $this->config = $container->get(ConfigInterface::class);
        $this->ipReader = $container->get(IPReaderInterface::class);
        $this->governanceManager = $container->get(DriverManager::class);
    }

    public function listen(): array
    {
        return [
            MainWorkerStart::class,
            MainCoroutineServerStart::class,
        ];
    }

    /**
     * @param object $event
     * @return void
     */
    public function process(object $event): void
    {
        $continue = true;
        while ($continue) {
            try {
                $servers = $this->getServers();

                foreach ($servers as $server) {
                    if (!isset($server['publish_to'])) {
                        continue;
                    }
                    $metaData = [
                        'server' => $server['name'],
                        'protocol' => 'http',
                        'publish_to' => $server['publish_to']
                    ];

                    if ($governance = $this->governanceManager->get($server['publish_to'])) {
                        if (!$governance->isRegistered($server['service_name'], $server['host'], (int)$server['port'], $metaData)) {
                            $governance->register($server['service_name'], $server['host'], (int)$server['port'], $metaData);
                        }
                    }
                }
                $continue = false;
            } catch (ServerException $throwable) {
                if (str_contains($throwable->getMessage(), 'Connection failed')) {
                    $this->logger->warning('Cannot register http service, connection of service center failed, re-register after 10 seconds.');
                    sleep(10);
                } else {
                    throw $throwable;
                }
            }
        }
    }

    protected function getServers(): array
    {
        $servers = $this->config->get('server.servers', []);

        foreach ($servers as &$server) {
            if (!isset($server['name'], $server['host'], $server['port'])) {
                continue;
            }
            if (!$server['name']) {
                throw new \InvalidArgumentException('Invalid server name');
            }
            $host = $server['host'];
            if (in_array($host, ['0.0.0.0', 'localhost'])) {
                $host = $this->ipReader->read();
            }
            if (!filter_var($host, FILTER_VALIDATE_IP)) {
                throw new \InvalidArgumentException(sprintf('Invalid host %s', $host));
            }
            $port = $server['port'];
            if (!is_numeric($port) || ($port < 0 || $port > 65535)) {
                throw new \InvalidArgumentException(sprintf('Invalid port %s', $port));
            }
            $port = (int)$port;

            $server['port'] = $port;
            $server['host'] = $host;
        }

        return $servers;
    }
}
